/*
 *  tracker/PatternEditor.h
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Milkytracker is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 *  PatternEditor.h
 *  MilkyTracker
 *
 *  Created by Peter Barth on 16.11.07.
 *
 */

#ifndef __PATTERNEDITOR_H__
#define __PATTERNEDITOR_H__

#include "EditorBase.h"
#include "PatternEditorTools.h"
#include "Undo.h"

struct TXMPattern;
class XModule;

class PatternEditor : public EditorBase
{
    public:
        // These are the clipboards, FT2 uses different clipboard buffers for
        // copy/cut/paste block/track/pattern operations, so we imitate these too
        enum ClipBoardTypes
        {
            ClipBoardTypeSelection,
            ClipBoardTypeTrack,
            ClipBoardTypePattern,
            // do not use
            ClipBoardTypeLAST
        };

        class PatternAdvanceInterface
        {
            public:
                virtual void advance() = 0;
        };

        class Selection
        {
            private:
                bool copyValid;
            public:
                PatternEditorTools::Position start, end, startCopy, endCopy;

                Selection ()
                {
                    reset();
                    startCopy = start;
                    endCopy = end;
                    copyValid = false;
                }

                void reset()
                {
                    start.channel = -1;
                    start.row = -1;
                    start.inner = 0;
                    end.channel = -1;
                    end.row = -1;
                    end.inner = 7;
                }

                bool isValid()
                {
                    return (start.channel >= 0 && start.row >= 0 &&
                            end.channel >= 0 && end.row >= 0);
                }

                bool isCopyValid()
                {
                    return (startCopy.channel >= 0 && startCopy.row >= 0 &&
                            endCopy.channel >= 0 && endCopy.row >= 0) && copyValid;
                }

                void backup();

                void restore();
        };

        struct TCommand
        {
            pp_uint8 effect;
            pp_uint8 operand;
        };
    private:
        class ClipBoard
        {
            private:
                mp_ubyte* buffer;

                PatternEditorTools::Position selectionStart, selectionEnd;

                pp_int32 selectionWidth;
                pp_int32 selectionHeight;

                // FT2 uses different clipboards for track/pattern/block operations
                // so a regular singleton design won't cut it
                static ClipBoard* instances[ClipBoardTypeLAST];

                ClipBoard ();
            public:
                ~ClipBoard ();

                static ClipBoard * getInstance(ClipBoardTypes type);

                void makeCopy(TXMPattern& pattern,
                              const PatternEditorTools::Position& ss, const PatternEditorTools::Position& se,
                              bool clear = false);
                void paste(TXMPattern& pattern, pp_int32 sc, pp_int32 sr, bool transparent = false);
                bool isEmpty() const {
                    return buffer == NULL;
                }

                pp_int32 getNumRows() {
                    return selectionHeight;
                }
                pp_int32 getNumChannels() {
                    return selectionWidth;
                }
        };

        // operations
        enum LastChanges
        {
            LastChangeNone,
            LastChangeSlotChange,
            LastChangeInsertNote,
            LastChangeInsertLine,
            LastChangeDeleteNote,
            LastChangeDeleteLine,
            LastChangeDeleteNoteVolumeAndEffect,
            LastChangeDeleteVolumeAndEffect,
            LastChangeDeleteEffect,
            LastChangeWriteMacro,
            LastChangeCut,
            LastChangePaste,
            LastChangeDeleteSelection,

            LastChangeExpandPattern,
            LastChangeShrinkPattern,
            LastChangeResizePattern,

            LastChangeLoadXPattern,
            LastChangeLoadXTrack,

            LastChangeInsRemapTrack,
            LastChangeInsRemapPattern,
            LastChangeInsRemapSelection,

            LastChangeNoteTransposeTrack,
            LastChangeNoteTransposePattern,
            LastChangeNoteTransposeSelection,

            LastChangeNoteInterpolate,
            LastChangeSplitTrack,
            LastChangeSwapChannels,
            LastChangeScaleVolume,

            LastChangeZeroOperandsTrack,
            LastChangeZeroOperandsPattern,
            LastChangeZeroOperandsSelection,

            LastChangeFillOperandsTrack,
            LastChangeFillOperandsPattern,
            LastChangeFillOperandsSelection,

            LastChangeRelocateCommandsTrack,
            LastChangeRelocateCommandsPattern,
            LastChangeRelocateCommandsSelection
        };
    private:
        TXMPattern* pattern;

        // Current cursor position
        PatternEditorTools::Position cursor;
        // Current selection
        Selection selection;

        pp_int32 numVisibleChannels;
        bool autoResize;
        pp_int32 currentInstrument;
        bool instrumentEnabled;
        bool instrumentBackTrace;
        pp_int32 currentOctave;

        // undo/redo information
        UndoStackEntry::UserData undoUserData;
        PatternUndoStackEntry* before;
        PPUndoStack<PatternUndoStackEntry>* undoStack;
        UndoHistory<TXMPattern, PatternUndoStackEntry>* undoHistory;
        LastChanges lastChange;
        bool lastOperationDidChangeRows;
        bool lastOperationDidChangeCursor;

        TCommand effectMacros[20];

        void prepareUndo();
        bool finishUndo(LastChanges lastChange, bool nonRepeat = false);

        bool revoke(const PatternUndoStackEntry* stackEntry);

        void cut(ClipBoard& clipBoard);
        void copy(ClipBoard& clipBoard);
        void paste(ClipBoard& clipBoard, bool transparent = false, pp_int32 fromChannel = -1);

        void clearRange(const PatternEditorTools::Position& rangeStart, const PatternEditorTools::Position& rangeEnd);
    public:
        PatternEditor ();
        virtual ~PatternEditor ();

        // query status
        bool getLastOperationDidChangeRows() const {
            return lastOperationDidChangeRows;
        }
        bool getLastOperationDidChangeCursor() const {
            return lastOperationDidChangeCursor;
        }

        void attachPattern(TXMPattern* pattern, XModule* module);
        TXMPattern * getPattern() {
            return pattern;
        }

        void reset();

        pp_int32 getNumChannels() const;
        pp_int32 getNumRows() const;

        void setNumVisibleChannels(pp_int32 numVisibleChannels) {
            this->numVisibleChannels = numVisibleChannels;
        }
        void setAutoResize(bool autoResize) {
            this->autoResize = autoResize;
        }
        bool getAutoResize() const {
            return autoResize;
        }

        // dealing with current cursor
        void setCursor(const PatternEditorTools::Position& cursor) {
            this->cursor = cursor;
        }
        PatternEditorTools::Position & getCursor() {
            return cursor;
        }

        void resetCursor() {
            cursor.row = cursor.channel = cursor.inner = 0;
        }

        // dealing with the selection
        void setSelection(const Selection& selection) {
            this->selection = selection;
        }
        Selection & getSelection() {
            return selection;
        }
        void setSelectionStart(const PatternEditorTools::Position& pos) {
            selection.start = pos;
        }
        void setSelectionEnd(const PatternEditorTools::Position& pos) {
            selection.end = pos;
        }
        void resetSelection() {
            selection.reset();
        }
        bool hasValidSelection();
        void selectChannel(pp_int32 channel);
        void selectAll();

        // dealing with instrument
        void setCurrentInstrument(pp_int32 ins) {
            currentInstrument = ins;
        }
        pp_int32 getCurrentActiveInstrument();
        void enableInstrument(bool instrumentEnabled) {
            this->instrumentEnabled = instrumentEnabled;
        }
        bool isInstrumentEnabled() {
            return instrumentEnabled;
        }
        // Intelligent instrument backtrace?
        void setInstrumentBackTrace(bool instrumentBackTrace) {
            this->instrumentBackTrace = instrumentBackTrace;
        }

        void setCurrentOctave(pp_int32 octave) {
            currentOctave = octave;
        }
        pp_int32 getCurrentOctave() const {
            return currentOctave;
        }
        void increaseCurrentOctave() {
            if (currentOctave < 8) currentOctave++;
        }
        void decreaseCurrentOctave() {
            if (currentOctave > 1) currentOctave--;
        }

        // --- Multilevel UNDO / REDO --------------------------------------------
        bool canUndo() const {
            if (undoStack) return !undoStack->IsEmpty();else return false;
        }
        bool canRedo() const {
            if (undoStack) return !undoStack->IsTop();else return false;
        }
        // undo last changes
        bool undo();
        // redo last changes
        bool redo();
        void setUndoUserData(const void* data, pp_uint32 dataLen) {
            this->undoUserData = UndoStackEntry::UserData((pp_uint8*)data, dataLen);
        }
        pp_uint32 getUndoUserDataLen() const {
            return undoUserData.getDataLen();
        }
        const void * getUndoUserData() const {
            return (void*)undoUserData.getData();
        }

        // --- dealing with the pattern data -------------------------------------
        void clearSelection();

        void clearPattern();

        bool clipBoardSelectionIsEmpty() const {
            return ClipBoard::getInstance(ClipBoardTypeSelection)->isEmpty();
        }

        void cut(ClipBoardTypes clipBoardType);
        void copy(ClipBoardTypes clipBoardType);
        void paste(ClipBoardTypes clipBoardType, bool transparent = false, pp_int32 fromChannel = -1);

        // resize pattern to a new number of rows
        bool resizePattern(pp_int32 newRowNum, bool withUndo = true);

        // insert a blank line after each pattern line
        bool expandPattern();

        // delete every odd pattern line
        bool shrinkPattern();

        // Load extended pattern from file (.XP)
        bool loadExtendedPattern(const PPSystemString& fileName);
        bool saveExtendedPattern(const PPSystemString& fileName);

        // Load extended track from file (.XT)
        bool loadExtendedTrack(const PPSystemString& fileName);
        bool saveExtendedTrack(const PPSystemString& fileName);

        // --- remap instruments -------------------------------------------------
        pp_int32 insRemapTrack(pp_int32 oldIns, pp_int32 newIns);
        pp_int32 insRemapPattern(pp_int32 oldIns, pp_int32 newIns);
        pp_int32 insRemapSelection(pp_int32 oldIns, pp_int32 newIns);

        // --- transpose notes ---------------------------------------------------
        pp_int32 noteTransposeTrackCore(const PatternEditorTools::TransposeParameters& transposeParameters, bool evaluate);
        pp_int32 noteTransposePatternCore(const PatternEditorTools::TransposeParameters& transposeParameters, bool evaluate);
        pp_int32 noteTransposeSelectionCore(const PatternEditorTools::TransposeParameters& transposeParameters, bool evaluate);

        // --- interpolate values in current selection ---------------------------
        pp_int32 interpolateValuesInSelection();

        // --- split track -------------------------------------------------------
        pp_int32 splitTrack(pp_int32 useChannels, bool selectionOnly, bool insertNoteOff);

        // --- swap channels -----------------------------------------------------
        pp_int32 swapChannels(pp_int32 dstChannel, pp_int32 srcChannel);

        // --- FT2 compatible scale volume function ------------------------------
        pp_int32 scaleVolume(const PatternEditorTools::Position& startSelection, const PatternEditorTools::Position& endSelection, float startScale, float endScale);
        pp_int32 scaleVolumeTrack(float startScale, float endScale);
        pp_int32 scaleVolumePattern(float startScale, float endScale);
        pp_int32 scaleVolumeSelection(float startScale, float endScale);

        // --- Zero out unecessary operands --------------------------------------
        pp_int32 zeroOperandsTrack(const PatternEditorTools::OperandOptimizeParameters& optimizeParameters, bool evaluate);
        pp_int32 zeroOperandsPattern(const PatternEditorTools::OperandOptimizeParameters& optimizeParameters, bool evaluate);
        pp_int32 zeroOperandsSelection(const PatternEditorTools::OperandOptimizeParameters& optimizeParameters, bool evaluate);

        // --- Fill in zero operands ---------------------------------------------
        pp_int32 fillOperandsTrack(const PatternEditorTools::OperandOptimizeParameters& optimizeParameters, bool evaluate);
        pp_int32 fillOperandsPattern(const PatternEditorTools::OperandOptimizeParameters& optimizeParameters, bool evaluate);
        pp_int32 fillOperandsSelection(const PatternEditorTools::OperandOptimizeParameters& optimizeParameters, bool evaluate);

        // --- Relocate FX into volume column if possible ------------------------
        pp_int32 relocateCommandsTrack(const PatternEditorTools::RelocateParameters& relocateParameters, bool evaluate);
        pp_int32 relocateCommandsPattern(const PatternEditorTools::RelocateParameters& relocateParameters, bool evaluate);
        pp_int32 relocateCommandsSelection(const PatternEditorTools::RelocateParameters& relocateParameters, bool evaluate);

        // --- write slot data ---------------------------------------------------
        bool writeNote(pp_int32 note,
                       bool withUndo = false,
                       PatternAdvanceInterface* advanceImpl = NULL);

        // --- write through, without undo etc. ----------------------------------
        void writeDirectNote(pp_int32 note,
                             pp_int32 track = -1,
                             pp_int32 row = -1,
                             pp_int32 order = -1);

        enum NibbleTypes
        {
            NibbleTypeLow,
            NibbleTypeHigh,
            NibbleTypeBoth
        };

        bool writeInstrument(NibbleTypes nibleType, pp_uint8 value, bool withUndo = false, PatternAdvanceInterface* advanceImpl = NULL);
        bool writeFT2Volume(NibbleTypes nibleType, pp_uint8 value, bool withUndo = false, PatternAdvanceInterface* advanceImpl = NULL);
        bool writeEffectNumber(pp_uint8 value, bool withUndo = false, PatternAdvanceInterface* advanceImpl = NULL);
        bool writeEffectOperand(NibbleTypes nibleType, pp_uint8 value, bool withUndo = false, PatternAdvanceInterface* advanceImpl = NULL);

        bool writeEffect(pp_int32 effNum, pp_uint8 eff, pp_uint8 op,
                         bool withUndo = false,
                         PatternAdvanceInterface* advanceImpl = NULL);

        // --- write through, without undo etc. ----------------------------------
        void writeDirectEffect(pp_int32 effNum, pp_uint8 eff, pp_uint8 op,
                               pp_int32 track = -1,
                               pp_int32 row = -1,
                               pp_int32 order = -1);

        // --- dealing with FT2 style effect macros ------------------------------
        void storeMacroFromCursor(pp_int32 slot);
        void writeMacroToCursor(pp_int32 slot, PatternAdvanceInterface* advanceImpl = NULL);

        void getMacroOperands(pp_int32 slot, pp_uint8& eff, pp_uint8& op);
        void setMacroOperands(pp_int32 slot, pp_uint8 eff, pp_uint8 op);

        // --- deleting slot data ------------------------------------------------
        void deleteCursorSlotData(PatternAdvanceInterface* advanceImpl = NULL);
        void deleteCursorSlotDataEntire(PatternAdvanceInterface* advanceImpl = NULL);
        void deleteCursorSlotDataVolumeAndEffect(PatternAdvanceInterface* advanceImpl = NULL);
        void deleteCursorSlotDataEffect(PatternAdvanceInterface* advanceImpl = NULL);

        // --- inserting/deleting entire rows ------------------------------------
        void insertNote(pp_int32 channel, pp_int32 row);
        void insertLine(pp_int32 row);
        void deleteNote(pp_int32 channel, pp_int32 row);
        void deleteLine(pp_int32 row);

};

#endif